#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <errno.h>
#include <time.h>

#define VERBOSE 1
#define TIME 1

int main( int argc, char* argv[] ){

	/* Declarations */

	struct sockaddr_in local;
	struct sockaddr_in remote;
	struct sockaddr remote2;
	struct sockaddr_in nodes[8];
	struct in_addr address[3];
	struct timespec time1;
	struct timespec time2;
	unsigned int *final = NULL;
	int connections = 0;
	int state[8];
	int store[3], end = 1;
	int pool;
	int lsock, asock, csock;
	int node = 0;
	int i, on = 1;
	int check, size;
	errno = 0;

	// Invocation Error handler
	if( argc  != 1 ){
		printf( "No arguments required...\n" );
		return -1;
	}

	// Initial Values Setup
	for( i = 0; i < 8; i++ ){
		state[i] = 0;
	}
	for( i = 0; i < 3; i++ ){
		store[i] = 0;
	}

	// LOCAL Address Setup
	bzero( &local, sizeof(local) );
	local.sin_family = AF_INET;
	local.sin_port = htons(20000);
	local.sin_addr.s_addr = INADDR_ANY;
	bzero( &(local.sin_zero), 8 );

	// Nodes Array Setup
	bzero( nodes, sizeof(struct sockaddr_in)*8 );
	for( i = 0; i < 8; i++ ){
		nodes[i].sin_family = AF_INET;
		nodes[i].sin_port = htons(20000);
		bzero( &(nodes[i].sin_zero), 8 );
	}

	// Socket Creation
	lsock = socket( AF_INET, SOCK_STREAM, 0 );
	if( lsock < 0 ){
		printf( "EMERGENCY OVERRIDE: SOCKET ERROR\n" );
		return -1;
	}

	// Address reuse setup
	check = setsockopt( lsock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on) );
	if( check < 0 ){
		printf( "%s\n", (char *)strerror(errno) );
		printf( "EMERGENCY OVERRIDE: SETOPT ERROR\n " );
		return -1;
	}

	// Bind socket to LOCAL
	check = bind( lsock, (struct sockaddr *)&local, sizeof(struct sockaddr) );
	if( check < 0 ){
		printf("%s\n", (char *)strerror(errno) );
		printf( "EMERGENCY OVERRIDE: BIND ERROR\n" );
		return -1;
	}

	// Listen to NODES
	check = listen( lsock, 10 );
	if( check < 0 ){
		printf( "EMERGENCY OVERRIDE: LISTEN ERROR\n" );
		return -1;
	}

	// Main Loop
	while( end ){

		// Accept Connection from NODE
		printf( "Waiting Conections...\n" );
		size = sizeof( struct sockaddr );
		asock = accept( lsock, (struct sockaddr *)&remote, &size );
		if( asock < 0 ){
			printf( "%s", (char *)strerror(errno) );
			printf( "EMERGENCY OVERRIDE: ACCEPT ERROR\n" );
			return -1;
		}
		
		clock_gettime( CLOCK_THREAD_CPUTIME_ID, &time1 );

		connections++;

		if(VERBOSE){ printf( "Acepted Connection [%d] from %s\n", connections, (char *)inet_ntoa(remote.sin_addr.s_addr) ); }

		if(connections == 14 ){
			end = 0;
		}

		// Search NODE address in Nodes List
		for( i = 0; i < node; i++ ){

			//If it's already there, exits for
			if( memcmp( &(nodes[i].sin_addr.s_addr), &(remote.sin_addr.s_addr), sizeof(remote.sin_addr.s_addr) ) == 0 ){
				pool = i;
				i = 9;
			}
		}
		// If it isn't there, copy his address to the list
		if( i < 8 ){
			pool = i;
			nodes[i].sin_addr.s_addr = remote.sin_addr.s_addr;
			node++;
		}
		
		// If there isn't a PARTNER available to the requesting NODE
		if( store[state[pool]] == 0 ){
			address[state[pool]].s_addr = remote.sin_addr.s_addr;

			// Send the NODE a message with size 0
			if(VERBOSE){ printf( "Sending wait signal...\n" ); }
			check = send( asock, &(address[state[pool]]), 0, 0);
			if( check < 0 ){
				printf( "EMERGENCY OVERRIDE: WAIT SEND ERROR\n");
				return -1;
			}
			store[state[pool]] = 1;
			state[pool]++;
			close(asock);

			// Elapsed Time Report
			clock_gettime( CLOCK_THREAD_CPUTIME_ID, &time2 );
			if(TIME){ printf( "Connection elapsed time [%s]: %d s %ld ns\n", (char *)inet_ntoa(local.sin_addr), (int)(time2.tv_sec-time1.tv_sec), time2.tv_nsec-time1.tv_nsec ); }

		// If there is a PARTNER available to the requesting NODE
		} else {

			// Send him the PARTNER address
			if(VERBOSE){ printf( "Sending partner: %s\n", (char *)inet_ntoa(address[state[pool]]) ); }
			check = send( asock, &(address[state[pool]]), sizeof(struct in_addr), 0);
			if( check < 0 ){
				printf( "EMERGENCY OVERRIDE: STORE SEND ERROR\n");
				return -1;
			}
			store[state[pool]] = 0;
			state[pool]++;
			close(asock);

			// Elapsed Time Report
			clock_gettime( CLOCK_THREAD_CPUTIME_ID, &time2 );
			if(TIME){ printf( "Connection elapsed time [%s]: %d s %ld ns\n", (char *)inet_ntoa(local.sin_addr), (int)(time2.tv_sec-time1.tv_sec), time2.tv_nsec-time1.tv_nsec ); }
		}
		// End Main Loop
	}

	clock_gettime( CLOCK_THREAD_CPUTIME_ID, &time1 );

	// Accept Connection from Final NODE
	if(VERBOSE){ printf( "Receiving final array...\n" ); }
	asock = accept( lsock, (struct sockaddr *)&remote, &size );
	if( asock < 0 ){
		printf( "EMERGENCY OVERRIDE: CFINAL ACCEPT ERROR\n" );
		return -1;
	}

	address[0].s_addr = remote.sin_addr.s_addr;
	
	// Receive Final Array 
	final = malloc( sizeof(unsigned int)*1000 );
	check = recv( asock, final, sizeof(unsigned int)*1000, 0 );
	if( check < 0 ){
		printf( "EMERGENCY OVERRIDE: CFINAL RECEIVE ERROR\n" );
		return -1;
	}
	if(VERBOSE){ printf( "Final Array Received...\n"); }

	close(lsock);

	// Elapsed Time Report
	clock_gettime( CLOCK_THREAD_CPUTIME_ID, &time2 );
	if(TIME){ printf( "Elapsed time [%s]: %d s %ld ns\n", (char *)inet_ntoa(local.sin_addr), (int)(time2.tv_sec-time1.tv_sec), time2.tv_nsec-time1.tv_nsec ); }

	// Socket Creation
	lsock = socket(AF_INET, SOCK_STREAM, 0 );
	if( lsock < 0 ){
		printf( "EMERGENCY OVERRIDE: CFINAL SOCKET ERROR\n" );
		return -1;
	}

	check = setsockopt( lsock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
	if( check < 0 ){
		printf( "EMERGENCY OVERRIDE: CFINAL SETSOCK ERROR\n" );
		return -1;
	}

	// Send Final Array to others NODES
	for( i = 0; i < 8; i++ ){

		clock_gettime( CLOCK_THREAD_CPUTIME_ID, &time1 );

		if(VERBOSE){ printf( "Propagating %s...\n", (char *)inet_ntoa( nodes[i].sin_addr ) ); }
		if( memcmp( &(address[0]), &(nodes[i].sin_addr.s_addr), sizeof(address[0]) ) != 0 ){

			// Create Socket
			csock = socket( AF_INET, SOCK_STREAM, 0 );
			if( csock < 0 ){
				printf( "EMERGENCY OVERRIDE: CFINAL SOCKET ERROR:%d\n", i );
				return -1;
			}

			// Connect 
			check = connect( csock, (struct sockaddr *)&(nodes[i]), sizeof(struct sockaddr) );
			if( check < 0 ){
				printf( "EMERGENCY OVERRIDE: CFINAL CONNECT ERROR: %d\n", i );
				return -1;
			}

			// Send Array
			check = send( csock, final, sizeof(unsigned int)*1000, 0 );
			if( check < 0 ){
				printf( "EMERGENCY OVERRIDE: CFINAL SEND ERROR: %d\n", i );
				return -1;
			}

			// Close Connection
			check = close( csock );
			if( check < 0 ){
				printf( "EMERGENCY OVERRIDE: CFINAL CLOSE ERROR: %d\n", i );
				return -1;
			}
			
			// Elapsed Time Report
			clock_gettime( CLOCK_THREAD_CPUTIME_ID, &time2 );
			if(TIME){ printf( "Elapsed time [%s]: %d s %ld ns\n", (char *)inet_ntoa(local.sin_addr), (int)(time2.tv_sec-time1.tv_sec), time2.tv_nsec-time1.tv_nsec ); }

		}
	}

	// Print results
	if(VERBOSE){
		printf( "Final Array: \n" );
		for( i = 0; i < 1000; i++ ){
			printf( "%u ", final[i] );
		}
		printf( "\n" );
	}

	free(final);
	close(asock);
	close(lsock);

	// Total Elapsed Time Report
	if(TIME){
		printf( "Total elapsed time [%s]: ", (char *)inet_ntoa(local.sin_addr) );
		clock_gettime( CLOCK_THREAD_CPUTIME_ID, &time1 );
		printf("%d s %ld ns\n ", (int)time1.tv_sec, time1.tv_nsec);
	}

	return 0;
}
